/* 
	@Copyright (c) 2007 by Denis Riabtchik. All rights reserved. See license.txt and http://jgrouse.com for details@
	$Id: Comment.java 574 2009-02-28 22:39:22Z denis.riabtchik $
*/

package jgrouse.jgdoc.elements;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jgrouse.jgdoc.JGDocException;
import jgrouse.jgdoc.TokenType;
import jgrouse.jgdoc.Vocabulary;
import jgrouse.jgdoc.api.IComment;
import jgrouse.jgdoc.api.ICommentTag;
import jgrouse.jgdoc.api.IContent;
import jgrouse.jgdoc.api.ILink;
import jgrouse.jgdoc.api.IParamSet;

public class Comment implements IComment, Serializable
{
	private static final long serialVersionUID = 1837567834343001902L;
	//private CommentTag m_content;
	private IContent m_content;
	private IContent m_summary;
	private ICommentTag m_commentType;
	
	private Map<Vocabulary, List<ICommentTag>> m_tags;
	private List<IParamSet> m_params;
	private String m_file;
	private int m_line;
	private transient ICommentTag m_currentParent;
	private ICommentTag m_returns;
	private String m_srcUrl;
	
	
	public Comment(String file, int line)
	{
		m_tags = new HashMap<Vocabulary, List<ICommentTag>>();
		m_params = new ArrayList<IParamSet>();
		m_file = file;
		m_line = line;
		m_currentParent = null;
	}
	
	private String tagModifiersToString(ICommentTag tag)
	{
		StringBuffer buf = new StringBuffer();
		Set<String> modifiers = tag.getModifiers();
		if (modifiers != null && modifiers.size() > 0)
		{
			modifiers = new LinkedHashSet<String>(modifiers);
			modifiers.remove("optional");
			if (modifiers.size() > 0)
			{
				buf.append("<modifiers name=\"");
				int i = 0;
				for (String s : modifiers)
				{
					if (s.length() > 0)
					{
						if (i > 0)
						{
							buf.append(' ');
						}
						buf.append(s);
						i++;
					}
				}			
				buf.append("\">");
				for (String s : modifiers)
				{
					if (s.length() > 0)
					{
						buf.append("<modifier name=\"").append(s).append("\"/>");
					}
				}
				buf.append("</modifiers>");
			}
		}		
		return buf.toString();
	}
	
	public String toString()
	{
		StringBuffer buf = new StringBuffer();
		String name = getName();
		buf.append("<comment>");
		if (name != null)
		{
			buf.append("<name>");
			if (m_commentType.getTagType().isNameAsDesc() && name != null)
			{
				buf.append("<![CDATA[").append(name.trim()).append("]]>");
			}
			else
			{
				buf.append(name);
			}
			buf.append("</name>");
		}
		IContent typeLink = getTypeLink();
		if (typeLink != null)
		{
			buf.append("<type>").append(typeLink).append("</type>");
		}
		buf.append(tagModifiersToString(m_commentType));
		
		if (getSummary() != null)
		{
			buf.append("<summary>").append(getSummary().toString()).append("</summary>");
		}
		if (getContent() != null)
		{
			buf.append("<commentContent>").append(getContent().toString()).append("</commentContent>\n");
		}
		for (IParamSet ps : getParamSets())
		{
			buf.append(ps.toString());
		}
		if (getReturnsTag() != null)
		{
			buf.append("<returns>");
			buf.append(getReturnsTag().toString(false));
			if (getReturnsTag().getChildren().size() > 0)
			{
				buf.append("<paramOptions>");
				for (ICommentTag ct : getReturnsTag().getChildren())
				{
					buf.append(tagAsParam(ct));
				}
				buf.append("</paramOptions>");
				
			}
			buf.append("</returns>");
		}
		for (Vocabulary voc : m_tags.keySet())
		{
			String vocString = voc.toString().toLowerCase();
			buf.append('<').append(vocString).append('>');
			for (ICommentTag ct : m_tags.get(voc))
			{
				buf.append(ct.toString());
			}
			buf.append("</").append(vocString).append(">\n");
		}
		if (m_srcUrl != null)
		{
			buf.append("<srcUrl>").append(m_srcUrl).append("</srcUrl>");
		}
		buf.append("</comment>\n");
		return buf.toString();
	}
	
	public List<ILink> collectLinks(List<ILink> pList)
	{
		if (m_content != null)
		{
			m_content.collectLinks(pList);
		}
		if (m_summary != null)
		{
			m_summary.collectLinks(pList);
		}
		if (m_commentType != null)
		{
			m_commentType.collectLinks(pList);
		}
		for (List<ICommentTag> l : m_tags.values())
		{
			for (ICommentTag ct : l)
			{
				ct.collectLinks(pList);
			}
		}
		if (m_returns != null)
		{
			m_returns.collectLinks(pList);
		}
		for (IParamSet ps : m_params)
		{
			ps.collectLinks(pList);
		}
		
		return pList;
	}
	
	/* (non-Javadoc)
	 * @see jgrouse.jgdoc.elements.IComment#getCommentType()
	 */
	public Vocabulary getCommentType()
	{
		return m_commentType == null? null : m_commentType.getTagType();
	}
	
	public TokenType getTokenType()
	{
		return m_commentType == null? null : m_commentType.getTagType().getTokenType();
	}
	
	
	private void addParam(ICommentTag tag)
	{
		IParamSet ps = null;
		if (m_params.size() == 0)
		{
			CommentTag pst = new CommentTag(Vocabulary.PARAMSET, null, null, "Syntax 1", null, this);
			ps = new ParamSet(pst);
			m_params.add(ps);
		}
		else
		{
			ps = m_params.get(m_params.size() - 1);
		}
		ps.addParam(tag);
		m_currentParent = tag;
	}
	
	public void addCommentTag(ICommentTag tag) throws JGDocException
	{
		TokenType type = tag.getTagType().getTokenType();
		
		if (tag.getTagType() != Vocabulary.PARAM && tag.getTagType() != Vocabulary.PARAMOPTION && tag.getTagType() != Vocabulary.RETURN && tag.getTagType() != Vocabulary.RETURNS)
		{
			m_currentParent = null;
		}
		if (tag.getTagType() == Vocabulary.RETURN || tag.getTagType() == Vocabulary.RETURNS)
		{
			m_currentParent = tag;
			if (m_returns != null)
			{
				throw new JGDocException("@return could be specified only once", m_file, m_line);
			}
			m_returns = tag;
			return;
		}
		
		if (type.isTagType())
		{
			if (m_commentType == null)
			{
				m_commentType = tag;
				String name = tag.getName() == null? null : tag.getName().trim();
				if (name == null || name.length() == 0)
				{
					if (tag.getTagType() == Vocabulary.SCOPE)
					{
						tag.setName(DocManager.GLOBAL_NAMESPACE);
					}
				}
				verifyCommentTags();
			}
			else
			{
				throw new JGDocException("Cannot use both " + m_commentType.getTagType() + " and " + tag.getTagType(), m_file, m_line);
			}
		} else
		{
			if (m_commentType != null)
			{
				TokenType tagType = m_commentType.getTagType().getTokenType(); 
				if (!tagType.isSubtagAllowed(type))
				{
					throw new JGDocException(tag.getTagType() + " cannot be used in " + m_commentType.getTagType(), m_file, m_line);
				}
			}
			if (tag.getTagType() == Vocabulary.PARAMSET)
			{
				if (tag.getContent() == null)
				{
					tag.setContent(new Content().addContentSection(new ContentSection("Syntax " + (m_params.size() + 1))));
				}
				m_params.add(new ParamSet(tag));
			}
			else if (tag.getTagType() == Vocabulary.PARAM)
			{
				addParam(tag);
			}
			else if (tag.getTagType() == Vocabulary.PARAMOPTION)
			{
				if (m_currentParent == null)
				{
					throw new JGDocException("No @param or @returns specified for @... " + tag.getName(), m_file, m_line);
				}
				m_currentParent.addChild(tag);
			}
			else
			{
				List<ICommentTag> current = m_tags.get(tag.getTagType());
				if (current == null)
				{
					current = new ArrayList<ICommentTag>();
					m_tags.put(tag.getTagType(), current);
				}
				if (current.size() < type.getArity())
				{
					current.add(tag);
				}
				else
				{
					throw new JGDocException(tag.getType() + " can appear in comment no more than " + type.getArity() + " times", m_file, m_line);
				}
				
			}
			
		}
		
		
	}
	
	private void verifyCommentTags() throws JGDocException
	{
		TokenType tokenType = m_commentType.getTagType().getTokenType();
		for (Vocabulary type : m_tags.keySet())
		{
			if (!tokenType.isSubtagAllowed(type.getTokenType()))
			{
				throw new JGDocException(type + " cannot be used in " + m_commentType.getTagType(), m_file, m_line);
			}
		}
	}

	public String getName()
	{
		if (m_commentType == null)
		{
			return null;
		}
		
		return m_commentType.getName();
	}
	
	public void setName(String pName) throws JGDocException
	{
		if (m_commentType == null)
		{
			throw new JGDocException("Invalid setting of name", m_file, m_line);
		}
		m_commentType.setName(pName);
	}
	
	public void resetPrivateModifier(String pPrivatePrefix)
	{
		m_commentType.resetPrivateModifier(pPrivatePrefix);
	}
	
	private String tagAsParam(ICommentTag ct)
	{
		StringBuffer buf = new StringBuffer();
		String tagName = ct.getTagType().toString().toLowerCase();
		buf.append("<").append(tagName).append(" name=\"").append(ct.getName());
		Set<String> mods = ct.getModifiers();
		if (mods != null && mods.contains("optional"))
		{
			buf.append("\" optional=\"true");
		}
		
		buf.append("\">\n");
		if (ct.getTypeLink() != null)
		{
			buf.append("<type>").append(ct.getTypeLink()).append("</type>");
		}
		buf.append(tagModifiersToString(ct));			
		buf.append(ct.getContent());
		if (ct.getChildren().size() > 0)
		{
			buf.append("<paramOptions>");
			for (ICommentTag child : ct.getChildren())
			{
				buf.append(tagAsParam(child));
			}
			buf.append("</paramOptions>");
		}
		buf.append("</").append(tagName).append(">");	
		return buf.toString();
	}	
	
	
	private class ParamSet implements IParamSet, Serializable
	{
		private static final long serialVersionUID = 1524945666738469380L;
		private ICommentTag m_paramSetTag;
		private List<ICommentTag> m_params;
		
		public ParamSet(ICommentTag pParamSetTag)
		{
			m_paramSetTag = pParamSetTag;
			m_params = new ArrayList<ICommentTag>();
		}
		
		public IContent getContent()
		{
			return m_paramSetTag.getContent();
		}
		
		
		public String toString()
		{
			StringBuffer buf = new StringBuffer();
			buf.append("<paramSet>\n");
			if (m_paramSetTag.getContent() != null)
			{
				buf.append("<paramSetDesc>").append(m_paramSetTag.getContent()).append("\n")
				.append("</paramSetDesc>");
			}
			for (ICommentTag ct : m_params)
			{
				buf.append(tagAsParam(ct));
			}
			buf.append("</paramSet>");
			return buf.toString();
		}
		
		public List<ILink> collectLinks(List<ILink> pList)
		{
			m_paramSetTag.collectLinks(pList);
			for (ICommentTag ct : m_params)
			{
				ct.collectLinks(pList);
			}
			return pList;
		}

		/* (non-Javadoc)
		 * @see jgrouse.jgdoc.elements.IParamSet#getParams()
		 */
		public List<ICommentTag> getParams()
		{
			return m_params;
		}

		public void setParams(List<ICommentTag> pParams)
		{
			m_params = pParams;
		}

		public ICommentTag getParamSetTag()
		{
			return m_paramSetTag;
		}

		public void setParamSetTag(ICommentTag pParamSetTag)
		{
			m_paramSetTag = pParamSetTag;
		}
		
		public void addParam(ICommentTag pParam)
		{
			m_params.add(pParam);
		}
		
	}
	
	public List<ICommentTag> getTagsForVocabulary(Vocabulary v)
	{
		return m_tags.get(v);
	}
	
	public void dropTagsForVocabulary(Vocabulary v)
	{
		m_tags.remove(v);
	}

	/* (non-Javadoc)
	 * @see jgrouse.jgdoc.elements.IComment#getContent()
	 */
	public IContent getContent()
	{
		return m_content;
	}

	public void setContent(IContent pContent)
	{
		m_content = pContent;
	}

	/* (non-Javadoc)
	 * @see jgrouse.jgdoc.elements.IComment#getSummary()
	 */
	public IContent getSummary()
	{
		return m_summary;
	}

	public void setSummary(IContent pSummary)
	{
		m_summary = pSummary;
	}

	public String getFile()
	{
		return m_file;
	}

	public int getLine()
	{
		return m_line;
	}

	/* (non-Javadoc)
	 * @see jgrouse.jgdoc.elements.IComment#getSrcUrl()
	 */
	public String getSrcUrl()
	{
		return m_srcUrl;
	}

	public void setSrcUrl(String pSrcUrl)
	{
		m_srcUrl = pSrcUrl;
	}

	public IContent getTypeLink()
	{
		return m_commentType.getTypeLink();
	}

	public String getModifiers()
	{
		return m_commentType.getStrModifiers();
	}
	
	public List<IParamSet> getParamSets()
	{
		return m_params;
	}
	
	public ICommentTag getReturnsTag()
	{
		return m_returns;
	}
	
	public List<String> getTagNames()
	{
		List<String> result = new LinkedList<String>();
		for (Vocabulary v : m_tags.keySet())
		{
			result.add(v.toString().toLowerCase());
		}
		return result;
	}
	
	
	public List<ICommentTag> get(String tagName)
	{
		Vocabulary v = Vocabulary.valueOf(tagName.toUpperCase());
		List<ICommentTag> result = m_tags.get(v);
		return result == null? new LinkedList<ICommentTag>() : result;
	}

	public boolean hasModifer(String pModifier)
	{
		return m_commentType.hasModifier(pModifier);
	}
	
	private void cleanupOneCT(ICommentTag ct)
	{
		Iterator<ICommentTag> iter = ct.getChildren().iterator();
		while (iter.hasNext())
		{
			ICommentTag child = iter.next();
			if (child.hasModifier("private"))
			{
				iter.remove();
			}
		}
	}

	public void cleanupPrivates()
	{
		for (IParamSet ps : m_params)
		{
			for (ICommentTag ct : ps.getParams())
			{
				cleanupOneCT(ct);
			}
		}
		ICommentTag ret = getReturnsTag();
		if (ret != null)
		{
			cleanupOneCT(ret);
		}
		
	}

}
